package jp.co.toshiba.ffsa.service.csde;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.SecretKeySpec;

import jp.co.toshiba.ffsa.common.DesignFile;
import jp.co.toshiba.ffsa.common.MetadataIn;
import jp.co.toshiba.ffsa.entity.AttachedFile;
import jp.co.toshiba.ffsa.entity.Project;
import jp.co.toshiba.ffsa.entity.User;
import jp.co.toshiba.ffsa.entity.Workflow;
import jp.co.toshiba.ffsa.mail.SendMaiMail;
import jp.co.toshiba.ffsa.service.common.CommonService;
import jp.co.toshiba.ffsa.service.common.ConstantValue;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.seasar.extension.jdbc.JdbcManager;
import org.seasar.extension.jdbc.where.SimpleWhere;
import org.seasar.framework.container.SingletonS2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.mai.mail.MailAddress;
import org.seasar.struts.util.MessageResourcesUtil;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.ozacc.mail.MailSendException;



/**
 * 設計I/F - Submit確認画面 サービスクラス
 * @author npc
 */
public class Csde601Service {

    /** メールアドレス */
    public String emailAddress;

    /** プロジェクトID */
    public String projectId;

    /** プロジェクト名 */
    public String projectName;

    /** ワークフローID */
    public String workflowId;

    /** ワークフロー名 */
    public String workflowName;

    /** レビジョン */
    public String revision;

    /** エラーコード */
    private String errorCode;

    /** ユーザID */
    private String userId;

    /** ユーザエンティティ */
    private User user;

    /** プロジェクトエンティティ */
    private Project project;

    /** ワークフローエンティティ */
    private Workflow workflow;

    /** ワークフーロ添付ファイルエンティティリスト */
    private List<AttachedFile> attachedFileList;

    /** サーバ上のディレクトリパス */
    private String ftpDirPath;

    /** ディレクトリ名　*/
    private String dir;

    /**
     * ワークフロー情報の更新
     * @return boolean
     * @throws Exception
     */
    public Boolean updateWorkflow() throws Exception {

        // S2Container初期化
        SingletonS2ContainerFactory.init();

        // S2ContainerからJdbcManagerを取得
        JdbcManager jdbcManager = SingletonS2Container.getComponent(JdbcManager.class);

        // 当日取得
        Calendar cal = Calendar.getInstance();
        Timestamp sysDateTime = new Timestamp(cal.getTime().getTime());

        // ワークフロー情報の更新
        workflow       = new Workflow();
        workflow.projectId      = projectId;
        workflow.workflowId     = workflowId;
        workflow.revision       = revision;
        workflow.status         = Integer.valueOf(ConstantValue.submitted); // 定数「3」
        workflow.submittedAt    = sysDateTime;
        workflow.updatedAt      = sysDateTime;
        workflow.updatedBy      = userId;

        // 登録
        int count = jdbcManager.update(workflow).includes("status"
                                                ,"submittedAt"
                                                ,"updatedAt"
                                                ,"updatedBy"
                                                ,"projectId"
                                                ,"workflowId"
                                                ,"revision"
                                                ).execute()
                                                ;
        if( count < 0 ){
            // ワークフロー情報の更新失敗
            errorCode = "csde601.904";
            return false;
        }
        return true;

    }

    /**
     * プロジェクト情報・ワークフロー添付ファイル情報取得し、メタデータ作成の処理
     * @return boolean
     * @throws Exception
     */
    public Boolean writeMetadata() throws Exception {

        // プロジェクト情報取得
        getProject();

        //ワークフロー添付ファイル情報取得
        getAttachedFileList();

        for (AttachedFile attachedFile : attachedFileList) {
            // 設計ファイルをFTPに送信
            if( !ftpSubmitFile(attachedFile.filePath, attachedFile.fileName) ){
                return false;
            }
        }

        // ワークフロー情報の更新
        if( !updateWorkflow() ){
            // ワークフロー情報の更新失敗
            return false;
        }
        // メタデータ作成
        if( !ftpSubmitEncryptMetadata(createMetadata()) ){
            // Submit失敗
            return false;
        }

        return true;

    }

    /**
     *  設計ファイルをFTPに送信
     *  FTPクライアントに設計ファイルをアップロードする
     * @param fileName 設計ファイル名
     * @throws Exception
     */
    public Boolean ftpSubmitFile(String filePath, String fileName) throws Exception {

        // エラーカウンタ
        int errorCount = 0;

        // FTPクライアントの作成
        FTPClient ftp = new FTPClient();
        ftp.connect(MessageResourcesUtil.getMessage("ftp_host"), Integer.valueOf(MessageResourcesUtil.getMessage("ftp_port")));
        if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
            //接続エラー時処理
            errorCode = "csde601.902";
            return false;
        }
        if (ftp.login(MessageResourcesUtil.getMessage("ftp_user"), MessageResourcesUtil.getMessage("ftp_password")) == false) {
            // ログインエラー処理
            errorCode = "csde601.903";
            return false;
        }

        // データの変換モードをバイナリに設定
        ftp.setFileType(FTP.BINARY_FILE_TYPE);

        // FTPディレクトリ生成
        ftpDirPath = MessageResourcesUtil.getMessage("designInterface.project.folderPath") + projectId + "/" + workflowId + "/" + revision + "/out";
        if( !makeDirectories(ftp, ftpDirPath) ){
            // ディレクトリ生成失敗
            errorCount += 1;
        }

        // 送信するファイルを読み込む
        FileInputStream   fis = null;

        fis = new FileInputStream(filePath + "/" + fileName);
        if( errorCount > 0 ){
            ftp.disconnect();
            IOUtils.closeQuietly(fis);
            return false;
        } else {
            // サーバ側ファイル生成(アップロード）
            ftp.storeFile(ftpDirPath + "/" + fileName, fis);
            ftp.disconnect();
            IOUtils.closeQuietly(fis);
            return true;
        }

    }

    /**
     * ディレクトリ生成
     * @param file
     * @throws Exception
     */
    public void makeDirs(String dir) throws Exception {

        File dirs = new File(dir);
        if (!dirs.exists()) {
            dirs.mkdirs();  //make folders
        }

    }

    /**
     * FTPサーバ上のファイルパス生成
     * @param ftpClient FTPクライアント
     * @param dir 生成するFTPサーバ上のファイルパス
     * @return boolean
     * @throws IOException
     */
    public Boolean makeDirectories(FTPClient ftpClient, String dir) throws IOException {

        String[] dirElements = dir.split("/");
        if (dirElements != null && dirElements.length > 0) {
            for (String singleDir : dirElements) {
                if( !"".equals(singleDir) ){
                    boolean existed = ftpClient.changeWorkingDirectory(singleDir);
                    if (!existed) {
                        boolean created = ftpClient.makeDirectory(singleDir);
                        if ( !created ) {
                            // ディレクトリ生成失敗
                            errorCode = "csde601.906";
                            return false;
                        }
                        ftpClient.changeWorkingDirectory(singleDir);
                    }
                }else{
                    // ディレクトリ初期化
                    ftpClient.changeWorkingDirectory("/");
                }
            }
        }
        return true;
    }

    /**
     * メタデータ送信（暗号化）
     *  FTPクライアントにメタデータを送信する
     * @param fileName メタデータファイル名
     * @throws Exception
     */
    public Boolean ftpSubmitEncryptMetadata(String metadata) throws Exception {

        // 当日取得
        Calendar cal          = Calendar.getInstance();
        SimpleDateFormat sdf  = new SimpleDateFormat("yyyyMMddHHmmss");
        Timestamp sysDateTime = new Timestamp(cal.getTime().getTime());
        String now            = sdf.format(sysDateTime);

        // FTPクライアントの作成
        FTPClient ftp = new FTPClient();
        ftp.connect(MessageResourcesUtil.getMessage("ftp_host"), Integer.valueOf(MessageResourcesUtil.getMessage("ftp_port")));
        if (!FTPReply.isPositiveCompletion(ftp.getReplyCode())) {
            //接続エラー時処理
            errorCode = "csde601.902";
            return false;
        }
        if (ftp.login(MessageResourcesUtil.getMessage("ftp_user"), MessageResourcesUtil.getMessage("ftp_password")) == false) {
            // FTPログインエラー処理
            errorCode = "csde601.903";
            return false;
        }

        // 暗号化
        Key key       = new SecretKeySpec(DigestUtils.sha256(ConstantValue.key), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);

        // データの変換モードをバイナリに設定
        ftp.setFileType(FTP.BINARY_FILE_TYPE);

        // FTPディレクトリ生成
        ftpDirPath = MessageResourcesUtil.getMessage("ftp.folderPath") + "out/";
        if( !makeDirectories(ftp, ftpDirPath) ){
            // ディレクトリ生成失敗
            return false;
        }

        InputStream        is        = null;
        OutputStream       out       = null;
        CipherOutputStream cos       = null;

        // メータデータ送信
        is = IOUtils.toInputStream(metadata);
        // FTP側

        out = ftp.storeFileStream(ftpDirPath + "FFSA_meta_" + projectId + "_" + now + ".txt");

        //暗号化
        cos       = new CipherOutputStream(out, cipher);
        out.write(cipher.getIV());

        byte[] a = new byte[8];
        int i = is.read(a);

        while (i != -1) {
            cos.write(a, 0, i);
            i = is.read(a);
        }

        //メータデータをAPP側に残す
        copyMetadata(dir, metadata, now);

        ftp.disconnect();
        cos.flush();
        IOUtils.closeQuietly(is);
        IOUtils.closeQuietly(out);
        IOUtils.closeQuietly(cos);
        return true;
    }

    /**
     * メータデータをAPP側に残す
     * @param filedir ディレクトリ
     * @param metadata　メータデータファイル
     * @param time　メータデータ生成時間
     * @throws Exception
     */
    public void copyMetadata(String filedir, String metadata, String time) throws Exception {
        // APPディレクトリ生成
        dir = MessageResourcesUtil.getMessage("ftp.folderPath") +"in/" ;
        makeDirs(dir);

        InputStream    is = null;
        FileOutputStream   fos = null;
        CipherOutputStream cos = null;

        // 文字列ストリム
        is = IOUtils.toInputStream(metadata);

        // 暗号化
        Key key = new SecretKeySpec(DigestUtils.sha256(ConstantValue.key), "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key);

        // 指定ディレクトリへファイル保存
        fos = new FileOutputStream(dir + "FFSA_meta_" + projectId + "_" + time + ".txt");
        cos = new CipherOutputStream(fos, cipher);
        fos.write(cipher.getIV());

        byte[] a = new byte[8];
        int i = is.read(a);

        while (i != -1) {
            cos.write(a, 0, i);
            i = is.read(a);
        }

        cos.flush();
        IOUtils.closeQuietly(is);
        IOUtils.closeQuietly(fos);
        IOUtils.closeQuietly(cos);
    }

    /**
     * ユーザー情報取得処理
     * @throws Exception
     */
    private void getUser() throws Exception{

        // S2Container初期化
        SingletonS2ContainerFactory.init();

        // S2ContainerからJdbcManagerを取得
        JdbcManager jdbcManager = SingletonS2Container.getComponent(JdbcManager.class);
        // 検索条件
        SimpleWhere swh = new SimpleWhere().eq("userID", userId)
                                           .eq("deleteFlag", ConstantValue.deleteFlagEnabled);

        // 一覧取得
        user= jdbcManager.from(User.class)
                         .where(swh)
                         .getSingleResult()
                         ;
        if(user != null){
            emailAddress = user.mailAddress;
        }

    }

    /**
     * メール送信処理
     * プロジェクト更新完了(顧客)
     * @return 処理結果
     * @throws Exception エラー
     */
    public Boolean mailSendToUser() throws Exception {
        boolean successFlag = false;
        try {
            // 顧客情報取得
            getUser();

            // メール情報をセット
            SendMaiMail sendMaiMail = new SendMaiMail();

            // 送信元メールアドレス(From)
            sendMaiMail.setMailAddressFrom(MessageResourcesUtil.getMessage("mail_address_from"));

            // 送信先メールアドレス(To)
            MailAddress[] sendMailAddressTo = new MailAddress[1];
            sendMailAddressTo[0] = new MailAddress(emailAddress,"");
            sendMaiMail.setMailAddressTo(sendMailAddressTo);

            // 置き換え文字列
            sendMaiMail.setReplacementItem1( projectName );
            sendMaiMail.setReplacementSubject("");

            // プロジェクト更新完了(顧客)
            successFlag = sendMaiMail.sendMail(ConstantValue.updProjectMailToCustomer);

            if(!successFlag){
                // エラーコード
                errorCode = "csde601.901";
                return false;
            }

        }catch(MailSendException m){
            return false;
        }catch(Exception e){
            e.printStackTrace();
            throw e;
        }
        return true;
    }

    /**
     * メール送信処理(担当者へ）
     * プロジェクト更新完了(管理者)
     * @return 処理結果
     * @throws Exception エラー
     */
    public Boolean mailSendToManager( ) throws Exception {
        boolean successFlag = false;
        try {
            // 顧客情報取得
            getUser();

            // メール情報をセット
            SendMaiMail sendMaiMail = new SendMaiMail();

            // 送信元メールアドレス(From)
            sendMaiMail.setMailAddressFrom(MessageResourcesUtil.getMessage("mail_address_from"));

            // 送信先メールアドレス(To)
            CommonService commonService = new CommonService();
            // 管理者全員のメールアドレスを取得する
            MailAddress[] mailAddress = commonService.getAdministratorAndSuperUserMailaddress(user.region);

            if( mailAddress == null ){
                errorCode = "mncm501.900";
                return false;
            }

            // 送信先メールアドレス
            sendMaiMail.setMailAddressTo(mailAddress);

            // 置き換え文字列
            sendMaiMail.setReplacementItem1( user.mailAddress );
            sendMaiMail.setReplacementItem2( projectName );
            sendMaiMail.setReplacementItem3( workflowName );
            sendMaiMail.setReplacementSubject("");

            // プロジェクト更新完了(管理者)
            successFlag = sendMaiMail.sendMail(ConstantValue.updProjectMailToAdministrator);

            if(!successFlag){
                // エラーコード
                errorCode = "csde601.901";
                return false;
            }

        }catch(MailSendException m){
            return false;
        }catch(Exception e){
            e.printStackTrace();
            throw e;
        }
        return true;
    }

    /**
     * メタデータ作成
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    private String createMetadata() throws NoSuchAlgorithmException, IOException {

        // ファイルのSHA1チェックサム
        String checksum = "";

        List<DesignFile> files = new ArrayList<DesignFile>();

        // ワークフーロ添付ファイル情報取得
        for (AttachedFile attachedFile : attachedFileList) {

            // ファイルのSHA1チェックサム確認
            checksum = verifyChecksum(attachedFile.filePath, attachedFile.fileName);

            files.add(new DesignFile(attachedFile.dataType, attachedFile.fileName, checksum));
        }

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String now           = sdf.format(new Date());
        String datetime      = sdf.format(workflow.submittedAt);

        MetadataIn in = new MetadataIn(now,
                ConstantValue.systemName,
                changeNull(projectId),
                userId,
                changeNull(project.contractNumber),
                changeNull(project.chipCornerName),
                workflowName,
                revision,
                datetime,
                ftpDirPath,
                files,
                MessageResourcesUtil.getMessage("csde601.007"),
                changeNull(project.technology),
                changeNull(project.base),
                changeNull(project.pkg),
                changeNull(project.gateSize),
                changeNull(project.targetFreq),
                changeNull(project.junctionTemperatureTjMax),
                changeNull(project.junctionTemperatureTjMin),
                changeNull(project.coreVoltageMax),
                changeNull(project.coreVoltageMin),
                changeNull(project.filesetType),
                changeNull(project.gateNetlistOption),
                changeNull(project.sdfOption));

        // 作成したメタデータからJSONオブジェクト作成
        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
        // ※このStringを暗号化してファイルに書き込んでください
        String metadata = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(in);
        System.out.println(metadata);
        return metadata;
    }

    /**
     * 「null」を「""」に変更
     * @param str 「""」
     * @return
     */
    private String changeNull(String str){

        if(str == null){
            str = "";
        }
        return str;

    }

    /**
     * プロジェクト情報取得処理
     * @throws Exception
     */
    private void getProject() throws Exception{

        // S2Container初期化
        SingletonS2ContainerFactory.init();

        // S2ContainerからJdbcManagerを取得
        JdbcManager jdbcManager = SingletonS2Container.getComponent(JdbcManager.class);

        // 検索条件
        SimpleWhere swh = new SimpleWhere().eq("projectId", projectId)
                                           .eq("deleteFlag", ConstantValue.deleteFlagEnabled);


        // 一覧取得
        project = jdbcManager.from(Project.class)
                         .where(swh)
                         .getSingleResult()
                         ;

    }

    /**
     * ワークフロー添付ファイル情報取得
     * @return
     * @throws Exception
     */
    private List<AttachedFile> getAttachedFileList() throws Exception{

        // S2Container初期化
        SingletonS2ContainerFactory.init();

        // S2ContainerからJdbcManagerを取得
        JdbcManager jdbcManager = SingletonS2Container.getComponent(JdbcManager.class);

        // 検索条件
        SimpleWhere swh = new SimpleWhere().eq("projectId", projectId)
                                           .eq("deleteFlag", ConstantValue.deleteFlagEnabled)
                                           ;
        // 一覧取得
        attachedFileList = jdbcManager.from(AttachedFile.class)
                         .where(swh)
                         .getResultList()
                         ;

        return attachedFileList;
    }

    /**
     * ファイルのSHA1チェックサム確認
     * @param FilePath and name 確認するファイル
     * @return sb.toString() チェックサム
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    private String verifyChecksum(String filePath, String file) throws NoSuchAlgorithmException, IOException    {
        MessageDigest sha1 = MessageDigest.getInstance("SHA1");

        FileInputStream   fis = null;

        fis = new FileInputStream(filePath + "/" + file);
        byte[] data = new byte[1024];
        int read    = 0;
        while ((read = fis.read(data)) != -1) {
            sha1.update(data, 0, read);
        };
        byte[] hashBytes = sha1.digest();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < hashBytes.length; i++) {
            sb.append(Integer.toString((hashBytes[i] & 0xff) + 0x100, 16).substring(1));
        }
        IOUtils.closeQuietly(fis);

        return sb.toString();
    }

    /**
     * 入力チェックエラーコードを取得する
     * @return errorCode
     */
    public String getErrorCode(){return this.errorCode;}

    /**
     * ユーザーIDをセットする
     * @param userId
     */
    public void setUserId(String userId) {this.userId = userId;}



}
